#include "PLMosaic.h"
#include "PLBlob.h"
#include "PLConversionMethods.h"
#include "PLDirectoryMethods.h"
#include "PLEdge.h"
#include "PLGlobals.h"
#include "PLStringMethods.h"
#include "PLTriangle.h"
#include "PLVertex.h"
#include "PLVertexFactory.h"

#include <fstream>
#include <iostream>

using namespace PL;

using std::cout;
using std::endl;
using std::getline;
using std::ifstream;
using std::ofstream;
using std::make_pair;
using std::map;
using std::pair;
using std::set;
using std::string;
using std::vector;

const PLuint U = 0U;
const PLuint V = 1U;
const PLuint X = 0U;
const PLuint Y = 1U;
const PLuint Z = 2U;
const PLuint NUM_ELEMENTS_PER_TEXCOORD = 2U;
const PLuint NUM_ELEMENTS_PER_VERTEX = 3U;
const PLuint PL_MODELFORMAT_OBJ_INDEX_OFFSET = 1U;

/**
  *	A PLTexcoordIndexPair object capsules a UV texture coordinate with its
  * corresponding index
  */
struct PLTexcoordIndexPair
{
	/**
	  *	Constructs a new PLTexcoordIndexPair object with the given arguments
	  *
	  * @param texcoord pointer to a double array containing a UV texture coordinate
	  * @param index index of the texture coordinate in the list of texture coordinates
	  * @param compareIndexes true when also the texcoord indexes should be compared in a stl container, false otherwise
	  */
	PLTexcoordIndexPair( double *texcoord, PLuint index, bool compareIndexes ) :
		mTexcoord( texcoord ),
		mIndex( index ),
		mCompareIndexes( compareIndexes ) {};

	/** Pointer to a double array containing a UV texture coordinate */
	double *mTexcoord;
	/** Index of the texture coordinate in the list of texture coordinates */
	PLuint mIndex;
	/** True when also the texcoord indexes should be compared in an stl container, false otherwise */
	bool mCompareIndexes;
};

/**
  *	PLTexcoordIndexPairPCompare objects do the same as PLTexcoordIndexPairCompare objects, but
  * expect const pointers instead of const references in the ()-operator
  */
class PLTexcoordIndexPairPCompare
{
public:
	/**
	  *	Overloaded ()-operator for the PLTexcoordIndexPairPCompare-Class, whose instances are
	  * used as Compare-objects in stl containers.
	  *	At first the texture coordinates are compared, first the U-coordinate, then
	  * the V-coordinate. When both mCompareIndexes members of first and second are
	  * true, also the texcoord indexes are compared. Whenever a value of first is smaller
	  * the corresponding value of second, true is returned, false otherwise.
	  *
	  * @param first const pointer to the first PLTexcoordIndexPair object in the comparison
	  * @param second const pointer to the second PLTexcoordIndexPair object in the comparison
	  * @return true when a single comparison value of first is smaller then the corresponding value of second, false otherwise
	  */
	bool operator()(const PLTexcoordIndexPair *first, const PLTexcoordIndexPair *second) const
	{
		if (first->mTexcoord[U] < second->mTexcoord[U])
		{
			return true;
		}
		else if (first->mTexcoord[U] > second->mTexcoord[U])
		{
			return false;
		}
		else
		{
			// Identical U-Values

			if (first->mTexcoord[V] < second->mTexcoord[V])
			{
				return true;
			}
			else if (first->mTexcoord[V] > second->mTexcoord[V])
			{
				return false;
			}
			else
			{
				// Identical V-Values

				if (!first->mCompareIndexes || !second->mCompareIndexes)
				{
					return false;
				}
				else
				{
					if (first->mIndex < second->mIndex)
					{
						return true;
					}
					else
					{
						return false;
					}
				}
			}
		}
	}
};

/**
  *	PLTexcoordIndexPairCompare objects are used as Compare-objects in stl containers,
  * when PLTexcoordIndexPair objects are stored (and not pointers).
  */
class PLTexcoordIndexPairCompare
{
public:
	/**
	  *	Overloaded ()-operator for the PLTexcoordIndexPairCompare-Class, whose instances are
	  * used as Compare-objects in stl containers.
	  *	At first the texture coordinates are compared, first the U-coordinate, then
	  * the V-coordinate. When both mCompareIndexes members of first and second are
	  * true, also the texcoord indexes are compared. Whenever a value of first is smaller
	  * the corresponding value of second, true is returned, false otherwise.
	  *
	  * @param first const reference to the first PLTexcoordIndexPair object in the comparison
	  * @param second const reference to the second PLTexcoordIndexPair object in the comparison
	  * @return true when a single comparison value of first is smaller then the corresponding value of second, false otherwise
	  */
	bool operator()(const PLTexcoordIndexPair &first, const PLTexcoordIndexPair &second) const
	{
		PLTexcoordIndexPairPCompare compare;
		return compare( &first, &second );
	}
};

///////////////////////////////////////////////////////////////////////////////

/**
  *	A PLVertexPosIndexPair object capsules an XYZ vertex position with its
  * corresponding index
  */
struct PLVertexPosIndexPair
{
	/**
	  *	Constructs a new PLTexcoordIndexPair object with the given arguments
	  *
	  * @param texcoord pointer to a double array containing a UV texture coordinate
	  * @param index index of the texture coordinate in the list of texture coordinates
	  * @param compareIndexes true when also the texcoord indexes should be compared in a stl container, false otherwise
	  */
	PLVertexPosIndexPair( double *vertexPos, PLuint index, bool compareIndexes ) :
		mVertexPos( vertexPos ), mIndex( index ), mCompareIndexes( compareIndexes ) {};
	
	/** Pointer to a double array containing an XZY vertex position */
	double *mVertexPos;
	/** Index of the vertex position in the list of vertex positions */
	PLuint mIndex;
	/** True when also the vertex indexes should be compared in an stl container, false otherwise */
	bool mCompareIndexes;
};

/**
  *	PLVertexPosIndexPairPCompare objects do the same as PLVertexPosIndexPairCompare objects, but
  * expect const pointers instead of const references in the ()-operator
  */
class PLVertexPosIndexPairPCompare
{
public:
	/**
	  *	Overloaded ()-operator for the PLVertexPosIndexPairPCompare-Class, whose instances are
	  * used as Compare-objects in stl containers.
	  *	At first the vertex positions are compared, first the X-, then
	  * the Y-, then the Z-coordinate. When both mCompareIndexes members of first and second are
	  * true, also the vertex indexes are compared. Whenever a value of first is smaller
	  * the corresponding value of second, true is returned, false otherwise.
	  *
	  * @param first const pointer to the first PLVertexPosIndexPair object in the comparison
	  * @param second const pointer to the second PLVertexPosIndexPair object in the comparison
	  * @return true when a single comparison value of first is smaller then the corresponding value of second, false otherwise
	  */
	bool operator()(const PLVertexPosIndexPair *first, const PLVertexPosIndexPair *second) const
	{
		if (first->mVertexPos[X] < second->mVertexPos[X])
		{
			return true;
		}
		else if (first->mVertexPos[X] > second->mVertexPos[X])
		{
			return false;
		}
		else
		{
			// Identical X-Values

			if (first->mVertexPos[Y] < second->mVertexPos[Y])
			{
				return true;
			}
			else if (first->mVertexPos[Y] > second->mVertexPos[Y])
			{
				return false;
			}
			else
			{
				// Identical Y-Values

				if (first->mVertexPos[Z] < second->mVertexPos[Z])
				{
					return true;
				}
				else if (first->mVertexPos[Z] > second->mVertexPos[Z])
				{
					return false;
				}
				else
				{
					// Identical Z-Values

					if (!first->mCompareIndexes || !second->mCompareIndexes)
					{
						return false;
					}
					else
					{
						if (first->mIndex < second->mIndex)
						{
							return true;
						}
						else
						{
							return false;
						}
					}
				}
			}
		}
	}
};

/**
  *	PLVertexPosIndexPairCompare objects are used as Compare-objects in stl containers,
  * when PLVertexPosIndexPair objects are stored (and not pointers).
  */
class PLVertexPosIndexPairCompare
{
public:
	/**
	  *	Overloaded ()-operator for the PLVertexPosIndexPairCompare-Class, whose instances are
	  * used as Compare-objects in stl containers.
	  *	At first the vertex positions are compared, first the X-, then
	  * the Y-, then the Z-coordinate. When both mCompareIndexes members of first and second are
	  * true, also the vertex indexes are compared. Whenever a value of first is smaller
	  * the corresponding value of second, true is returned, false otherwise.
	  *
	  * @param first const reference to the first PLVertexPosIndexPair object in the comparison
	  * @param second const reference to the second PLVertexPosIndexPair object in the comparison
	  * @return true when a single comparison value of first is smaller then the corresponding value of second, false otherwise
	  */
	bool operator()(const PLVertexPosIndexPair &first, const PLVertexPosIndexPair &second) const
	{
		PLVertexPosIndexPairPCompare compare;
		return compare( &first, &second );
	}
};

/**
  *	Constructs a new PLMosaic object with the given arguments.
  *
  * @param modelFilePath full path of the model file
  * @param modelFileFormat enum value specifying the model file format
  * @param leveledModelFilePath full path of the leveled model file
  * @param leveledModelFileFormat enum value specifying the leveled model file format
  * @param filteringMode filtering mode for sampling the colors in the images
  * @param outlineVertexNormalLength length of the vertex normals in pixels, important for the triangle ring calculation
  * @param imageFormat enum value specifying the image format of the generated image files
  */
PLMosaic::PLMosaic( const string &modelFilePath,
	const PLModelFormat &modelFileFormat,
	const std::string &leveledModelFilePath,
	const PLModelFormat &leveledModelFileFormat,
	const PLFilteringMode &filteringMode,
	PLuint outlineVertexNormalLength,
	const PLImageFormat &imageFormat ) throw( PLIOException, PLFileNotFoundException )
{
	mFilteringMode = filteringMode;
	mOutlineVertexNormalLength = outlineVertexNormalLength;
	mNumVertexesInModel = 0;

	mLeveledModelFilePath = leveledModelFilePath;
	mLeveledModelFileFormat = leveledModelFileFormat;

	cout << "Loading..." << endl;
	LoadModelFile( modelFilePath, modelFileFormat, imageFormat );

	cout << "Updating Vertexes Indexes..." << endl;
	for(map<string, set<PLBlob*>>::const_iterator it = mMaterialNamesToBlobsMap.begin();
		it != mMaterialNamesToBlobsMap.end(); it++)
	{
		for(set<PLBlob*>::iterator blob_it = it->second.begin();
			blob_it != it->second.end(); blob_it++)
		{
			(*blob_it)->UpdateOutlineVertexesIndexes();
		}
	}

	cout << "Finding Adjacent Edges..." << endl;
	FindAdjacentEdges();

	cout << "Assigning color values to the vertexes..." << endl;
	AssignOriginalColorValuesToVertexes();

	cout << "Calculating blob outline paths..." << endl;
	CalculateBlobOutlinePaths();

	cout << "Calculating blob outline vertex normals..." << endl;
	CalculateBlobOutlineVertexNormals();

	cout << "Calculating blob outline triangle rings..." << endl;
	CalculateBlobOutlineTriangleRings();
}

/**
  *	Destroys the PLMosaic object and all of the data structures that belong to
  * it (PLTriangle, PLEdge and PLVertex objects).
  */
PLMosaic::~PLMosaic( void )
{
	// Delete the Texture Coordinates
	for (vector<double*>::iterator it = mTextureCoordinates.begin();
		it != mTextureCoordinates.end(); it++)
	{
		delete [] *it;
	}

	// Delete all the Blobs
	for(map<string, set<PLBlob*>>::iterator it = mMaterialNamesToBlobsMap.begin();
		it != mMaterialNamesToBlobsMap.end(); it++)
	{
		for(set<PLBlob*>::iterator blob_it = it->second.begin();
			blob_it != it->second.end(); blob_it++)
		{
			(*blob_it)->DeleteData();
 			delete *blob_it;
		}
	}

	// Delete the sets with the equal vertex indexes
	set<set<PLuint>*> already_deleted_sets;
	for (map<PLuint, set<PLuint>*>::iterator it = mEqualVertexIndexesMap.begin();
		it != mEqualVertexIndexesMap.end(); it++)
	{
		if(already_deleted_sets.find( it->second ) == already_deleted_sets.end())
		{
			already_deleted_sets.insert( it->second );
			delete it->second;
		}
	}

	// Delete the sets with the equal texcoord indexes
	already_deleted_sets.clear();
	for (map<PLuint, set<PLuint>*>::iterator it = mEqualTexcoordIndexesMap.begin();
		it != mEqualTexcoordIndexesMap.end(); it++)
	{
		if(already_deleted_sets.find( it->second ) == already_deleted_sets.end())
		{
			already_deleted_sets.insert( it->second );
			delete it->second;
		}
	}
}

/**
  *	Returns the base path of the underlying model file
  *
  * @return base path of the underlying model file
  */
const string &PLMosaic::GetBasePath( void ) const
{
	return mBasePath;
}

/**
  *	Returns the map, that maps material names to sets with all the pointers to PLBlob objects that correspond to one particular image
  *
  * @return map, that maps material names to sets with all the pointers to PLBlob objects that correspond to one particular image
  */
const map<string, set<PLBlob*>> &PLMosaic::GetMaterialNamesToBlobsMap( void ) const
{
	return mMaterialNamesToBlobsMap;
}

/**
  *	Returns the map, that maps material names to the corresponding image names
  *
  * @return map, that maps material names to the corresponding image names
  */
const map<string, string> &PLMosaic::GetMaterialNamesToImageNamesMap( void ) const
{
	return mMaterialNamesToImageNamesMap;
}

/**
  *	Returns the map, that maps material names to the suffixes strings of the generated image files
  *
  * @return map, that maps material names to the suffixes strings of the generated image files
  */
const std::map<std::string, std::string> &PLMosaic::GetMaterialNamesToLevelledTextureSuffixesMap( void ) const
{
	return mMaterialNamesToLevelledTextureSuffixesMap;
}

/**
  *	Returns the map, that maps image names (not paths!) to the corresponding PLImageInfo objects
  *
  * @return map, that maps image names to the corresponding PLImageInfo objects
  */
const map<string, PLImageInfo> &PLMosaic::GetImageNamesToImageInfosMap( void ) const
{
	return mImageNamesToImageInfosMap;
}

/**
  *	Returns the offset used for the vertex and texcoord indices. It is 1, when the indices start with 1, 0 otherwise
  *
  * @return offset used for the vertex and texcoord indices
  */
const PLuint &PLMosaic::GetIndexOffset( void ) const
{
	return mIndexOffset;
}

/**
  *	Returns a const reference to the vector containing the UV texture coordinates
  *
  * @return const reference to the vector containing the UV texture coordinates
  */
const vector<double*> &PLMosaic::GetTextureCoordinates( void ) const
{
	return mTextureCoordinates;
}

/**
  *	Assigns the original colors to all the vertexes by sampling the images at the
  * texture coordinate positions. The sampling method passed to the constructor is used.
  */
void PLMosaic::AssignOriginalColorValuesToVertexes( void ) throw( PLFileNotFoundException )
{
	IplImage *img = 0;

	string image_name;

	for (map<string, set<PLBlob*>>::const_iterator image_it = mMaterialNamesToBlobsMap.begin();
		image_it != mMaterialNamesToBlobsMap.end(); image_it++)
	{
		image_name = mMaterialNamesToImageNamesMap[image_it->first];

		img = cvLoadImage( (mBasePath + PL_FILE_SEPARATOR + image_name).c_str() );

		if (!img)
			throw PLFileNotFoundException( "Could not find image file " + mBasePath + PL_FILE_SEPARATOR + image_name );

		mImageNamesToImageInfosMap.insert(
			make_pair( image_name, PLImageInfo( img->width, img->height ) ) );

		for (set<PLBlob*>::const_iterator blob_it = image_it->second.begin();
			blob_it != image_it->second.end(); blob_it++)
		{
			(*blob_it)->AssignOriginalColorValuesToVertexes( img, mTextureCoordinates, mFilteringMode );
		}

		// release the image
		cvReleaseImage( &img );
	}
}

/**
  *	Calculates the outline paths for all PLBlob objects for all images.
  */
void PLMosaic::CalculateBlobOutlinePaths( void )
{
	for (map<string, set<PLBlob*>>::const_iterator image_it = mMaterialNamesToBlobsMap.begin();
		image_it != mMaterialNamesToBlobsMap.end(); image_it++)
	{
		for (set<PLBlob*>::const_iterator blob_it = image_it->second.begin();
			blob_it != image_it->second.end(); blob_it++)
		{
			(*blob_it)->CalculateOutlineEdgePaths();
		}
	}
}

/**
  *	Calculates the outline triangle ring for all PLBlob objects for all images.
  */
void PLMosaic::CalculateBlobOutlineTriangleRings( void )
{
	PLImageInfo *image_info;

	for (map<string, set<PLBlob*>>::const_iterator image_it = mMaterialNamesToBlobsMap.begin();
		image_it != mMaterialNamesToBlobsMap.end(); image_it++)
	{
		image_info = &mImageNamesToImageInfosMap.find( mMaterialNamesToImageNamesMap[image_it->first] )->second;
		for (set<PLBlob*>::const_iterator blob_it = image_it->second.begin();
			blob_it != image_it->second.end(); blob_it++)
		{
			mNumVertexesInModel += (*blob_it)->CalculateOutlineTriangleRing( mOutlineVertexNormalLength,
				image_info->width, image_info->height, mNumVertexesInModel, &mTextureCoordinates,
				mIndexOffset == 1U ? true : false );
		}
	}
}

/**
  *	Calculates the outline vertex normals for all PLBlob objects for all images.
  */
void PLMosaic::CalculateBlobOutlineVertexNormals( void )
{
	for (map<string, set<PLBlob*>>::const_iterator image_it = mMaterialNamesToBlobsMap.begin();
		image_it != mMaterialNamesToBlobsMap.end(); image_it++)
	{
		for (set<PLBlob*>::const_iterator blob_it = image_it->second.begin();
			blob_it != image_it->second.end(); blob_it++)
		{
			(*blob_it)->CalculateOutlineVertexNormals( mTextureCoordinates );
		}
	}
}

/**
  *	Calculates the Leveling function for the R, G and B channel. After that,
  * one can use a PLImageFactory object to generate the leveled images
  *
  * @param options desired options for the leveling function calculation
  */
void PLMosaic::CalculateLevellingFunction( const PLPlanarPolygon3dsOptimizationOptions *options )
{
	//PLPlanarPolygon3dsOptimizationOptions options( 0.0, 1000U, 1000U, 1.0e-6, 100.0 );
	PLPlanarPolygon3dsOptimization solver( &mMaterialNamesToBlobsMap, *options );

//	cout << "Number of variables: " << solver.GetNumberOfVariables() << endl;
// 	cout << "Number of vertexes in Factory: " << PLVertexFactory::Get()->GetNumberOfBorrowedVertexes() << endl;
// 	cout << "Sum Borrows in Factory: " << PLVertexFactory::Get()->GetSumBorrows() << endl;
// 	cout << "Max usage of vertex in Factory: " << PLVertexFactory::Get()->GetMaxUsageOfVertex() << endl;
// 	cout << "Min usage of vertex in Factory: " << PLVertexFactory::Get()->GetMinUsageOfVertex() << endl;

// 	// Solve for the red channel
	solver.SetCurrentChannel( PL_CHANNEL_RED );
	solver.Solve();

	// Solve for the green channel
	solver.SetCurrentChannel( PL_CHANNEL_GREEN );
	solver.Solve();

	// Solve for the blue channel
	solver.SetCurrentChannel( PL_CHANNEL_BLUE );
	solver.Solve();
}

/**
  *	Finds the adjacent edges. Two edges are adjacent when both sides are textured
  * by different images.
  */
void PLMosaic::FindAdjacentEdges( void )
{
	PLEdge *current_edge;
	PLBlob *current_blob;
	PLEdge *corresponding_edge;

	map<string, map<string, PLuint>> material_names_to_relations_map;
	map<string, map<string, PLuint>>::iterator test_it;

	map<string, PLuint> *current_map;
	map<string, PLuint>::iterator map_it;

	// For every material
	for (map<string, set<PLBlob*>>::const_iterator image_it = mMaterialNamesToBlobsMap.begin();
		image_it != mMaterialNamesToBlobsMap.end(); image_it++)
	{
		test_it = material_names_to_relations_map.insert( make_pair( image_it->first, map<string, PLuint>() ) ).first;
		current_map = &test_it->second;

		// For every Blob inside the material-image
		for (set<PLBlob*>::const_iterator blob_it = image_it->second.begin();
			blob_it != image_it->second.end(); blob_it++)
		{
			// For every edge inside the Blob
			for (set<PLEdge*, PLEdge::PLEdgeVIPCompare>::const_iterator edge_it = (*blob_it)->GetOutlineEdgeSet().begin();
				edge_it != (*blob_it)->GetOutlineEdgeSet().end(); edge_it++)
			{
				current_edge = *edge_it;

				// Look for the current_edge in all other images in all blobs

				// For every other image
				for (map<string, set<PLBlob*>>::const_iterator other_image_it = mMaterialNamesToBlobsMap.begin();
					other_image_it != mMaterialNamesToBlobsMap.end(); other_image_it++)
				{
					if (other_image_it == image_it)
					{
						// Continue, because it is the original material-image itself
						continue;
					}

					// For every Blob inside the other image
					for (set<PLBlob*>::const_iterator other_blob_it = other_image_it->second.begin();
						other_blob_it != other_image_it->second.end(); other_blob_it++)
					{
						current_blob = *other_blob_it;

						// Look, if there is an edge that corresponds to the current_edge
						
						// Test at first just for the vertexes for performance reasons
						if (current_blob->HasOutlineVertexIndex( current_edge->FromVertexIndex() ) &&
							current_blob->HasOutlineVertexIndex( current_edge->ToVertexIndex() ))
						{
							// OK, both vertexes are inside, so check for the whole edge
							corresponding_edge = current_blob->GetCorrespondingEdge( current_edge );

							if (corresponding_edge)
							{
								// YES, this edge corresponds to the current_edge

								current_edge->SetAdjacentEdge( corresponding_edge );

								current_edge->From().AddAdjacentVertex( &corresponding_edge->To() );
								current_edge->To().AddAdjacentVertex( &corresponding_edge->From() );

								map_it = current_map->insert( make_pair( other_image_it->first, 0U ) ).first;
								map_it->second++;
							}
						}
					}
				}
			}
		}
	}
}

/**
  *	Returns the first element of the set of equal texcoord indexes. Two texcoord
  * indexes are considered as equal, when the two texture coordinates, the texcoord
  * indexes refer to, are equal.
  *
  * @param indexToken std::string representation of the texcoord index for which the first element of the corresponding set is returned
  * @return first element of the set of equal texcoord indexes
  */
PLuint PLMosaic::GetFirstElementFromEqualTexcoordIndexes( const string &indexToken ) const
{	
	PLuint token_as_uint32 = StringToPLuint( indexToken );
	std::map<PLuint, std::set<PLuint>*>::const_iterator it =
		mEqualTexcoordIndexesMap.find( token_as_uint32 );

	if (it != mEqualTexcoordIndexesMap.end())
	{
		return (*(it->second->begin()));
	}

	return token_as_uint32;
}

/**
  *	Returns the first element of the set of equal vertex indexes. Two vertex
  * indexes are considered as equal, when the two vertex positions, the vertex
  * indexes refer to, are equal.
  *
  * @param indexToken std::string representation of the vertex index for which the first element of the corresponding set is returned
  * @return first element of the set of equal vertex indexes
  */
PLuint PLMosaic::GetFirstElementFromEqualVertexIndexes( const string &indexToken ) const
{	
	PLuint token_as_uint32 = StringToPLuint( indexToken );
	std::map<PLuint, std::set<PLuint>*>::const_iterator it =
		mEqualVertexIndexesMap.find( token_as_uint32 );

	if (it != mEqualVertexIndexesMap.end())
	{
		return (*(it->second->begin()));
	}

	return token_as_uint32;
}

/**
  *	Loads the model file from hard disc
  *
  * @param modelFilePath full path of the model file
  * @param modelFileFormat enum value specifying the model file format
  * @param imageFormat enum value specifying the image format of the generated image files
  */
void PLMosaic::LoadModelFile( const std::string &modelFilePath,
	const PLModelFormat &modelFileFormat,
	const PLImageFormat &imageFormat ) throw( PLIOException )
{
	string current_line;
	ifstream model_file ( modelFilePath );
	ifstream material_file;

	mBasePath = GetFileBasePath( modelFilePath );
	string material_file_path;
	string material_name;
	//string current_image_name;
	vector<string> face_tokens;
	vector<string> tokens;
	vector<string>::const_iterator tokens_it;

	set<PLBlob*> *current_blob_set = 0;
	set<PLBlob*>::iterator blob_set_it;

	vector<PLEdge*> current_face_edges;

	// Much too slow!!!
	//vector<double*> vertex_positions;

	set<PLVertexPosIndexPair, PLVertexPosIndexPairCompare> vertex_positions;
	set<PLVertexPosIndexPair, PLVertexPosIndexPairCompare>::iterator vertex_pos_it;

	set<PLTexcoordIndexPair, PLTexcoordIndexPairCompare> texcoords;
	set<PLTexcoordIndexPair, PLTexcoordIndexPairCompare>::iterator texcoords_it;

	//vector<double*>::const_iterator vertex_it;
	//vector<double*> texture_coords;

	map<string, string> material_to_image_map;
	map<string, string>::const_iterator material_it;

	map<PLuint, PLBlob*> edge_index_to_blob_map;

	map<PLBlob*, vector<PLuint>> blob_to_edge_indices_map;
	map<PLBlob*, vector<PLuint>>::iterator blob_it;

	map<PLuint, set<PLuint>*>::iterator equal_vertex_it;
	map<PLuint, set<PLuint>*>::iterator equal_texcoord_it;

	map<string, PLuint> image_name_to_num_occurences_map;

	PLuint edge_count = 0;
	PLuint current_image_id = 0;
	PLuint image_count = 0;
	set<PLuint> new_edge_indices;

	bool inside_textured_faces = true;

	double *current_vertex_pos = 0;
	double *current_texture_coord = 0;

	PLuint vertex_count = 0U;
	PLuint face_count = 0U;

	bool valid_model_file_format = true;

	switch (modelFileFormat)
	{
		case PL_MODELFORMAT_OBJ:
		{
			mIndexOffset = PL_MODELFORMAT_OBJ_INDEX_OFFSET;
			break;
		}
		default:
		{
			mIndexOffset = 0;
			valid_model_file_format = false;
			break;
		}
	}

	if (model_file.is_open())
	{
		while (model_file.good())
		{
			if (!valid_model_file_format)
				break;

			tokens.clear();
			getline( model_file, current_line );
			Tokenize( current_line, &tokens );

			switch (modelFileFormat)
			{
				case PL_MODELFORMAT_OBJ:
				{
					if (tokens.size() > 0)
					{
						if (tokens[0] != "mtllib")
						{
							mLeveledModelFileLines.push_back( current_line );
						}

						if (tokens[0] == "v")
						{
 							// Vertex coordinate
							current_vertex_pos =
								new double[NUM_ELEMENTS_PER_VERTEX];
							
							current_vertex_pos[X] =
								StringToDouble( tokens[1] );
							current_vertex_pos[Y] =
								StringToDouble( tokens[2] );
							current_vertex_pos[Z] =
								StringToDouble( tokens[3] );

							PLVertexPosIndexPair vertex_index_pair( current_vertex_pos, static_cast<PLuint>( vertex_positions.size() ) + PL_MODELFORMAT_OBJ_INDEX_OFFSET, false );

							// Look, if the vertex already exists
							vertex_pos_it = vertex_positions.find( vertex_index_pair );

							if (vertex_pos_it != vertex_positions.end())
							{
								// The vertex already exists

								equal_vertex_it = mEqualVertexIndexesMap.find( vertex_pos_it->mIndex );

								if (equal_vertex_it == mEqualVertexIndexesMap.end())
								{
									// New equal pair
									set<PLuint> *new_indexes_set = new set<PLuint>();

									new_indexes_set->insert( vertex_pos_it->mIndex );
									new_indexes_set->insert( vertex_positions.size() + PL_MODELFORMAT_OBJ_INDEX_OFFSET );

									equal_vertex_it = mEqualVertexIndexesMap.insert( make_pair( vertex_pos_it->mIndex, new_indexes_set ) ).first;
									mEqualVertexIndexesMap.insert( make_pair( vertex_positions.size() + PL_MODELFORMAT_OBJ_INDEX_OFFSET, new_indexes_set ) );
								}
								else
								{
									// There are already equal vertexes
									equal_vertex_it->second->insert( vertex_positions.size() + PL_MODELFORMAT_OBJ_INDEX_OFFSET );
									mEqualVertexIndexesMap.insert( make_pair( vertex_positions.size() + PL_MODELFORMAT_OBJ_INDEX_OFFSET, equal_vertex_it->second ) );
								}
							}

							// It is important that indexes are compared when inserted
							vertex_index_pair.mCompareIndexes = true;

							vertex_count++;

							if (!vertex_positions.insert( vertex_index_pair ).second)
							{
								cout << "could not add!!!!" << endl;
							}
						}
						else if (tokens[0] == "vt")
						{
							// Texture coordinate
							current_texture_coord =
								new double[NUM_ELEMENTS_PER_TEXCOORD];

							current_texture_coord[U] =
								StringToDouble( tokens[1] );
							current_texture_coord[V] =
								StringToDouble( tokens[2] );

							PLTexcoordIndexPair texcoord_index_pair( current_texture_coord, static_cast<PLuint>( texcoords.size() ) + PL_MODELFORMAT_OBJ_INDEX_OFFSET, false );

							// Look, if the texcoord already exists
							texcoords_it = texcoords.find( texcoord_index_pair );

							if (texcoords_it != texcoords.end())
							{
								// The texcoord already exists

								equal_texcoord_it = mEqualTexcoordIndexesMap.find( texcoords_it->mIndex );

								if (equal_texcoord_it == mEqualTexcoordIndexesMap.end())
								{
									// New equal pair
									set<PLuint> *new_indexes_set = new set<PLuint>();

									new_indexes_set->insert( texcoords_it->mIndex );
									new_indexes_set->insert( texcoords.size() + PL_MODELFORMAT_OBJ_INDEX_OFFSET );

									equal_texcoord_it = mEqualTexcoordIndexesMap.insert( make_pair( texcoords_it->mIndex, new_indexes_set ) ).first;
									mEqualTexcoordIndexesMap.insert( make_pair( texcoords.size() + PL_MODELFORMAT_OBJ_INDEX_OFFSET, new_indexes_set ) );
								}
								else
								{
									// There are already equal texcoords
									equal_texcoord_it->second->insert( texcoords.size() + PL_MODELFORMAT_OBJ_INDEX_OFFSET );
									mEqualTexcoordIndexesMap.insert( make_pair( texcoords.size() + PL_MODELFORMAT_OBJ_INDEX_OFFSET, equal_texcoord_it->second ) );
								}

							}

							// It is important that indexes are compared when inserted
							texcoord_index_pair.mCompareIndexes = true;

							//vertex_count++;

							if (!texcoords.insert( texcoord_index_pair ).second)
							{
								cout << "could not add!!!!" << endl;
							}

							mTextureCoordinates.push_back( current_texture_coord );
						}
						else if (tokens[0] == "f" && inside_textured_faces)
						{
							// Face
							face_count++;

							current_face_edges.clear();
							new_edge_indices.clear();
							edge_count = 0;
							
							face_tokens.clear();
							Tokenize( tokens[1], &face_tokens, "/" );

							// Store only one index for equal vertex positions

							PLuint first_vertex = GetFirstElementFromEqualVertexIndexes( face_tokens[0] );
							PLuint first_texcoord = GetFirstElementFromEqualTexcoordIndexes( face_tokens[1] );

							PLuint current_vertex;
							PLuint current_texcoord;

							PLuint last_vertex = first_vertex;
							PLuint last_texcoord = first_texcoord;

							// Find all edges of the current face
							for (PLuint i = 2U; i < tokens.size(); i++)
							{
								face_tokens.clear();
								Tokenize( tokens[i], &face_tokens, "/" );

								// Store only one index for equal vertex positions
								current_vertex = GetFirstElementFromEqualVertexIndexes( face_tokens[0] );
								current_texcoord = GetFirstElementFromEqualTexcoordIndexes( face_tokens[1] );

								current_face_edges.push_back( new PLEdge(last_vertex, last_texcoord, current_vertex, current_texcoord, current_image_id) );
								new_edge_indices.insert( edge_count++ );

								last_vertex = current_vertex;
								last_texcoord = current_texcoord;
							}

							// Edge from last to first vertex of the current face
							current_face_edges.push_back( new PLEdge(last_vertex, last_texcoord, first_vertex, first_texcoord, current_image_id) );
							new_edge_indices.insert( edge_count++ );

							edge_index_to_blob_map.clear();

							// Find those edges that are already in use
							for (PLuint edge_index = 0; edge_index < current_face_edges.size(); edge_index++)
							{
								for (blob_set_it = current_blob_set->begin(); blob_set_it != current_blob_set->end(); blob_set_it++)
								{
									if ((*blob_set_it)->HasOutlineEdge( current_face_edges[edge_index] ))
									{
										edge_index_to_blob_map.insert( make_pair( edge_index, *blob_set_it ) );
										break;
									}
								}
							}

							// Analyze the edge_index_to_blob_map
							if (edge_index_to_blob_map.size() == 0)
							{
								// Face is in a new blob

								PLBlob *blob = new PLBlob();
								PLTriangle *triangle = new PLTriangle( current_face_edges );

								for (vector<PLEdge*>::const_iterator it = current_face_edges.begin(); it != current_face_edges.end(); it++)
								{
									blob->AddOutlineEdge( *it );
								}

								blob->AddTriangle( triangle );

								current_blob_set->insert( blob );
							} else if (edge_index_to_blob_map.size() == 1)
							{
								// The Face corresponds to one specific blob
								// The sharing edge has to be deleted!

								PLuint sharing_edge_index = edge_index_to_blob_map.begin()->first;
								PLBlob *blob = edge_index_to_blob_map.begin()->second;

								PLTriangle *triangle = new PLTriangle( current_face_edges );
								blob->AddTriangle( triangle );

								for (PLuint i = 0; i < current_face_edges.size(); i++)
								{
									if (i != sharing_edge_index)
										blob->AddOutlineEdge( current_face_edges[i] );
									else
									{
										blob->RemoveOutlineEdge( current_face_edges[i] );
										
										// delete current_face_edges[i];
										// current_face_edges[i] = 0;
									}
								}
							} else
							{
								// Face corresponds to more than one blob
								// Now it gets a bit complicated

								blob_to_edge_indices_map.clear();

								for (map<PLuint, PLBlob*>::const_iterator it = edge_index_to_blob_map.begin(); it != edge_index_to_blob_map.end(); it++)
								{
									blob_it = blob_to_edge_indices_map.find( it->second );

									if (blob_it == blob_to_edge_indices_map.end())
									{
										// Blob is not in the map
										blob_it = blob_to_edge_indices_map.insert( make_pair( it->second, vector<PLuint>() ) ).first;
									}

									blob_it->second.push_back( it->first );
								}

								PLBlob *final_blob = blob_to_edge_indices_map.begin()->first;

								for (blob_it = blob_to_edge_indices_map.begin(); blob_it != blob_to_edge_indices_map.end(); blob_it++)
								{
									// Delete the shared edges
									for (vector<PLuint>::const_iterator it = blob_it->second.begin(); it != blob_it->second.end(); it++)
									{
										blob_it->first->RemoveOutlineEdge( current_face_edges[*it] );

										new_edge_indices.erase( *it );
									}

									if (blob_it->first != final_blob)
									{
										// Another blob, merge with it
										final_blob->Merge( *blob_it->first );

										// Delete the old blob which is now merged into the final one
										current_blob_set->erase( blob_it->first );
										delete blob_it->first;
									}
								}

								// Add all new edges to the final blob
								for (set<PLuint>::const_iterator it = new_edge_indices.begin(); it != new_edge_indices.end(); it++)
								{
									final_blob->AddOutlineEdge( current_face_edges[*it] );
								}

								PLTriangle *triangle = new PLTriangle( current_face_edges );
								final_blob->AddTriangle( triangle );
							}
						}
						else if(tokens[0] == "usemtl")
						{
							material_it =
								material_to_image_map.find( tokens[1] );

							map<string, vector<PLBlob*>>::iterator it;

							// Material Usage Declaration
							if (material_it != material_to_image_map.end())
							{
								inside_textured_faces = true;

								pair<map<string, set<PLBlob*>>::iterator, bool> result = mMaterialNamesToBlobsMap.insert( make_pair( material_it->first, set<PLBlob*>() ) );
								mMaterialNamesToImageNamesMap.insert( *material_it );
								
								current_blob_set = &result.first->second;

								if (result.second)
								{
									// New material name

									mMaterialNamesToImageIdsMap.insert( make_pair( material_it->first, ++image_count ) );
									mImageIdsToMaterialNamesMap.insert( make_pair( image_count, material_it->first  ) );
									current_image_id = image_count;
								} else
								{
									// Material already exists in the map
									current_image_id = mMaterialNamesToImageIdsMap.find( material_it->first )->second;
								}
							}
							else
							{
								inside_textured_faces = false;
							}
						}
						else if (tokens[0] == "#")
						{
							// This is a comment, indicated by '#'
							continue;
						}
						else if (tokens[0] == "mtllib")
						{
							mLeveledModelFileLines.push_back( "mtllib ./" + PL::GetFileNameFromPath( mLeveledModelFilePath ) + ".mtl" );

							material_file_path = mBasePath + PL_FILE_SEPARATOR +
								tokens[1];

							mLeveledMaterialFilePath = mBasePath + PL_FILE_SEPARATOR +
								RemoveFileExtension( RemoveFileExtension( tokens[1] ) ) +
								"_levelled.obj.mtl";

							material_file.open( material_file_path );

							// fill the material_to_image_map

							if (material_file.is_open())
							{
								mLeveledMaterialFileLines.clear();

								while (material_file.good())
								{
									tokens.clear();
									getline( material_file, current_line );
									Tokenize( current_line, &tokens );
									
									if (tokens.size() > 0)
									{
										if (tokens[0] == "newmtl")
										{
											// New Material Definition
											material_name = tokens[1];
											mLeveledMaterialFileLines.push_back( current_line );
										}
										else if (tokens[0] == "map_Kd")
										{
											// Image corresponding to the Material
											material_to_image_map[material_name] =
												tokens[1];

											pair<map<string, PLuint>::iterator, bool> insert_pair = image_name_to_num_occurences_map.insert( make_pair ( tokens[1], 0u ) );

											insert_pair.first->second++;

											mMaterialNamesToLevelledTextureSuffixesMap[material_name] = PLuintToString( insert_pair.first->second );
											mLeveledMaterialFileLines.push_back( "map_Kd " + RemoveFileExtension( tokens[1] ) + "_" +
												mMaterialNamesToLevelledTextureSuffixesMap[material_name] + PL_LEVELLED_FILE_NAME_SUFFIX + PL_IMAGEFORMAT_FILE_EXTENSIONS[imageFormat] );
										}
										else
										{
											mLeveledMaterialFileLines.push_back( current_line );
										}
									}
									else
									{
										mLeveledMaterialFileLines.push_back( "\n" );
									}
								}
							}
							else
							{
								for (vertex_pos_it = vertex_positions.begin();
									vertex_pos_it != vertex_positions.end(); vertex_pos_it++)
								{
									delete [] vertex_pos_it->mVertexPos;
								}

								throw PLIOException( "Could not open material file " + modelFilePath );
							}

							material_file.close();
						}
					}
					else
					{
						mLeveledModelFileLines.push_back( "\n" );
					}

					break;	
				}
				
				default:
				{
					mIsNull = true;
					valid_model_file_format = false;
					break;
				}
			}
		}
		model_file.close();

//		DEBUG OUTPUT
// 		PLuint blob_number;
// 		PLBlob *blob;
// 		PLEdge *edge;
// 		const std::set<PLEdge*, PLEdge::PLEdgePCompare> *edge_set;
// 
// 		for (map<string, set<PLBlob*>>::const_iterator it = mImageNamesToBlobsMap.begin(); it != mImageNamesToBlobsMap.end(); it++)
// 		{
// 			cout << it->first << endl;
// 
// 			blob_number = 0;
// 			for (set<PLBlob*>::const_iterator s_it = it->second.begin(); s_it != it->second.end(); s_it++)
// 			{
// 				blob = *s_it;
// 				edge_set = &blob->GetEdgeSet();
// 				edge = *edge_set->begin();
// 
// 				cout << "\tBlob Nr. " << blob_number++ << ": " << (*s_it)->GetEdgeSet().size() << endl;
// 				cout << "\t\tFirst Edge: " << edge->FromVertexIndex() << "/" << edge->FromTexcoordIndex() << " " <<
// 					edge->ToVertexIndex() << "/" << edge->ToTexcoordIndex() << endl;
// 			}
// 		}
//
// 		for (map<string, string>::const_iterator it = material_to_image_map.begin(); 
// 			it != material_to_image_map.end(); it++)
// 		{
// 			cout << it->first << " - " << it->second << endl;
// 		}
// 
// 		for (vector<double*>::const_iterator it = texture_coords.begin();
// 			it != texture_coords.end(); it++)
// 		{
// 			for (PLuint i = 0; i < NUM_ELEMENTS_PER_TEXCOORD; i++)
// 			{
// 				cout << (*it)[i] << " ";
// 			}
// 
// 			cout << endl;
// 		}

// 		for (equal_vertex_it = mEqualVertexIndexesMap.begin(); equal_vertex_it != mEqualVertexIndexesMap.end(); equal_vertex_it++)
// 		{
// 			cout << equal_vertex_it->first << ": ";
// 			for (set<PLuint>::iterator it = equal_vertex_it->second->begin(); it != equal_vertex_it->second->end(); it++)
// 			{
// 				cout << *it << ", ";
// 			}
// 			cout << endl;
// 		}

		cout << "Num faces in the model: " << face_count << endl;

		mNumVertexesInModel += vertex_positions.size();

		// Delete the Vertex Positions
		for (vertex_pos_it = vertex_positions.begin();
			vertex_pos_it != vertex_positions.end(); vertex_pos_it++)
		{
			delete [] vertex_pos_it->mVertexPos;
		}
	}
	else
	{
		for (vertex_pos_it = vertex_positions.begin();
			vertex_pos_it != vertex_positions.end(); vertex_pos_it++)
		{
			delete [] vertex_pos_it->mVertexPos;
		}

		throw PLIOException( "Could not open model file " + modelFilePath );
	}
}

/**
  *	Writes the leveled model file to disc. The location was passed to the
  * constructor.
  *
  * @return true when the generation was successful, false otherwise
  */
bool PLMosaic::WriteLeveledModelFile( void ) const
{
	string path = mLeveledModelFilePath;

	ofstream model_file;
	ofstream material_file;

	switch (mLeveledModelFileFormat)
	{
	case PL_MODELFORMAT_OBJ:
		if (!PL::StringEndsWith( path, ".obj"))
		{
			path += ".obj";
		}

		model_file.open ( path.c_str() );

		if (!model_file.is_open())
			return false;

		for (vector<string>::const_iterator it = mLeveledModelFileLines.begin();
			it != mLeveledModelFileLines.end(); it++)
		{
			if (*it != "\n")
				model_file << *it << "\n";
			else
				model_file << "\n";
		}

		model_file.close();

		material_file.open ( (path + ".mtl").c_str() );

		if (!material_file.is_open())
			return false;

		for (vector<string>::const_iterator it = mLeveledMaterialFileLines.begin();
			it != mLeveledMaterialFileLines.end(); it++)
		{
			if (*it != "\n")
				material_file << *it << "\n";
			else
				material_file << "\n";
		}

		material_file.close();

		return true;

	default:
		return false;
	}
}